﻿using Blm.Utils.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace VisionCloud.Web.Authorize
{
    /// <summary>
    /// 外部接口自定义授权
    /// </summary>
    /// <remarks>
    /// 权限验证步骤：
    /// 1、读取请求体body内容，并使用SHA256加密body内容
    /// 2、组装明文：api=接口路由&version=版本号&date=13位时间戳&username=用户名&SHA-256=第1步加密得到的内容
    /// 3、加密明文：使用username对应的secret加密明文，加密方式为HmacSHA256
    /// </remarks>
    public class ExternalAuthorizationHandler : AuthorizationHandler<ExternalAuthorizationRequirement>
    {
        /// <summary>
        /// 策略名称
        /// </summary>
        public const string PolicyName = "ExternalAuth";
        private readonly IHttpContextAccessor httpContextAccessor;
        private readonly string[] appVersionArray;
        private readonly List<ExternalUser> externalUsers = new List<ExternalUser>();

        public ExternalAuthorizationHandler(IConfiguration cfg,
            IHttpContextAccessor httpContextAccessor)
        {
            this.httpContextAccessor = httpContextAccessor;
            appVersionArray = cfg.GetSection("AppVersion").Get<string[]>();
            externalUsers = cfg.GetSection("ExternalUsers").Get<List<ExternalUser>>();
        }

        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ExternalAuthorizationRequirement requirement)
        {
            var authorizationFailedContext = new AuthorizationFailedContext { Message = "没有访问权限" };
            var httpContext = httpContextAccessor.HttpContext;
            var request = httpContext.Request;
            if (request.Method.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase))
            {
                context.Succeed(requirement);
                return;
            }

            _ = request.Headers.TryGetValue("Authorization", out var tokenValue);
            _ = request.Headers.TryGetValue("date", out var dateValue);
            _ = request.Headers.TryGetValue("appVersion", out var appVersionValue);
            if (StringValues.IsNullOrEmpty(tokenValue) || StringValues.IsNullOrEmpty(dateValue) || StringValues.IsNullOrEmpty(appVersionValue))
            {
                AuthFailSet(context, authorizationFailedContext, httpContext);
                return;
            }
            var token = tokenValue.ToString();
            _ = long.TryParse(dateValue, out var date);
            var appVersion = appVersionValue.ToString();
            if (!token.StartsWith("hmac") || Math.Abs(date).ToString().Length != 13 || !appVersionArray.Contains(appVersion))
            {
                AuthFailSet(context, authorizationFailedContext, httpContext);
                return;
            }

            var arr = token.Split(",");
            if (arr.Length != 2)
            {
                AuthFailSet(context, authorizationFailedContext, httpContext);
                return;
            }
            var username = arr[0].Replace("hmac", "").Replace("username=", "").Trim();
            var signature = arr[1];
            if (!signature.StartsWith("signature"))
            {
                AuthFailSet(context, authorizationFailedContext, httpContext);
                return;
            }
            var user = externalUsers.FirstOrDefault(p => p.UserName == username);
            if (user == null)
            {
                AuthFailSet(context, authorizationFailedContext, httpContext, $"{username}用户不存在");
                return;
            }

            signature = signature.Replace("signature=", "").Trim();
            var bodyContent = await GetRequestBody(request);
            var planText = $"api={request.Path}&version=1.0&date={date}&username={username}&SHA-256={SecurityHelper.GetSHA256Str(bodyContent)}";
            var cipherText = SecurityHelper.GetHmacSHA256Str(planText, user.Secret);
            if (signature != cipherText)
            {
                AuthFailSet(context, authorizationFailedContext, httpContext);
                return;
            }

            context.Succeed(requirement);

            static void AuthFailSet(AuthorizationHandlerContext context, AuthorizationFailedContext authorizationFailedContext, HttpContext httpContext, string message = null)
            {
                context.Fail();
                if (!string.IsNullOrEmpty(message))
                    authorizationFailedContext.Message = message;
                httpContext.Features.Set(authorizationFailedContext);
            }
        }

        /// <summary>
        /// 读取http请求的body内容
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        private async Task<string> GetRequestBody(HttpRequest request)
        {
            request.EnableBuffering();
            using var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true);
            var body = await reader.ReadToEndAsync();
            request.Body.Position = 0;
            return body;
        }
    }

    public class ExternalUser
    {
        /// <summary>
        /// 用户名
        /// </summary>
        public string UserName { get; set; }

        /// <summary>
        /// 秘钥
        /// </summary>
        public string Secret { get; set; }
    }
}
